using AutoMapper;
using MediatR;
using Microsoft.EntityFrameworkCore;
using S.M.SmartMedical.API.Read.Application.Query;
using S.M.SmartMedical.Domain.DTOs;
using S.M.SmartMedical.Domain.Entities;
using S.M.SmartMedical.ErrorCode;
using S.M.SmartMedical.Infrastructure.BaseRepository;
using System.Text.Json;

namespace S.M.SmartMedical.API.Read.Application.Handler
{
    /// <summary>
    /// 获取自助开单院区列表查询处理器
    /// </summary>
    public class GetHospitalAreasHandler : IRequestHandler<GetHospitalAreasQuery, APIResult<List<HospitalAreaDto>>>
    {
        private readonly IRepository<HospitalArea> _hospitalAreaRepository;
        private readonly IMapper _mapper;
        private readonly ILogger<GetHospitalAreasHandler> _logger;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="hospitalAreaRepository">院区仓储</param>
        /// <param name="mapper">对象映射器</param>
        /// <param name="logger">日志记录器</param>
        public GetHospitalAreasHandler(
            IRepository<HospitalArea> hospitalAreaRepository,
            IMapper mapper,
            ILogger<GetHospitalAreasHandler> logger)
        {
            _hospitalAreaRepository = hospitalAreaRepository;
            _mapper = mapper;
            _logger = logger;
        }

        /// <summary>
        /// 处理获取院区列表查询
        /// </summary>
        /// <param name="request">查询请求</param>
        /// <param name="cancellationToken">取消令牌</param>
        /// <returns>院区列表响应</returns>
        public async Task<APIResult<List<HospitalAreaDto>>> Handle(GetHospitalAreasQuery request, CancellationToken cancellationToken)
        {
            try
            {
                _logger.LogInformation("开始获取自助开单院区列表，搜索关键词：{SearchKeyword}", request.SearchKeyword);

                // 构建查询条件
                var query = _hospitalAreaRepository.GetAllAsync()
                    .Where(x => !x.IsDeleted && x.IsEnabled); // 过滤已删除和未启用的院区

                // 如果只查询支持自助开单的院区
                if (request.OnlySelfServiceEnabled)
                {
                    query = query.Where(x => x.IsSelfServiceEnabled);
                }

                // 如果有搜索关键词，按名称或地址搜索
                if (!string.IsNullOrWhiteSpace(request.SearchKeyword))
                {
                    var keyword = request.SearchKeyword.Trim();
                    query = query.Where(x => x.Name.Contains(keyword) || x.Address.Contains(keyword));
                }

                // 按排序顺序和创建时间排序
                query = query.OrderBy(x => x.SortOrder).ThenByDescending(x => x.CreateTime);

                // 执行查询
                var hospitalAreas = await query.ToListAsync(cancellationToken);

                // 映射到DTO
                var hospitalAreaDtos = new List<HospitalAreaDto>();
                foreach (var area in hospitalAreas)
                {
                    var dto = _mapper.Map<HospitalAreaDto>(area);
                    
                    // 解析标签JSON字符串
                    if (!string.IsNullOrWhiteSpace(area.Tags))
                    {
                        try
                        {
                            dto.Tags = JsonSerializer.Deserialize<List<string>>(area.Tags) ?? new List<string>();
                        }
                        catch (JsonException ex)
                        {
                            _logger.LogWarning("解析院区标签JSON失败，院区ID：{AreaId}，错误：{Error}", area.Id, ex.Message);
                            dto.Tags = new List<string>();
                        }
                    }

                    // 如果提供了用户位置，计算距离
                    if (request.UserLatitude.HasValue && request.UserLongitude.HasValue &&
                        area.Latitude.HasValue && area.Longitude.HasValue)
                    {
                        dto.Distance = CalculateDistance(
                            request.UserLatitude.Value, request.UserLongitude.Value,
                            area.Latitude.Value, area.Longitude.Value);
                    }

                    hospitalAreaDtos.Add(dto);
                }

                // 如果计算了距离，按距离重新排序
                if (request.UserLatitude.HasValue && request.UserLongitude.HasValue)
                {
                    hospitalAreaDtos = hospitalAreaDtos.OrderBy(x => x.Distance ?? decimal.MaxValue).ToList();
                }

                _logger.LogInformation("成功获取到 {Count} 个院区信息", hospitalAreaDtos.Count);

                return new APIResult<List<HospitalAreaDto>>
                {
                    Code = APIEnum.OK,
                    Message = "获取院区列表成功",
                    Data = hospitalAreaDtos
                };
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "获取自助开单院区列表时发生错误");
                return new APIResult<List<HospitalAreaDto>>
                {
                    Code = APIEnum.Fail,
                    Message = "获取院区列表失败，请稍后重试",
                    Data = new List<HospitalAreaDto>()
                };
            }
        }

        /// <summary>
        /// 计算两个地理坐标之间的距离（公里）
        /// 使用Haversine公式计算球面距离
        /// </summary>
        /// <param name="lat1">起点纬度</param>
        /// <param name="lon1">起点经度</param>
        /// <param name="lat2">终点纬度</param>
        /// <param name="lon2">终点经度</param>
        /// <returns>距离（公里）</returns>
        private static decimal CalculateDistance(decimal lat1, decimal lon1, decimal lat2, decimal lon2)
        {
            const double R = 6371; // 地球半径（公里）

            var dLat = ToRadians((double)(lat2 - lat1));
            var dLon = ToRadians((double)(lon2 - lon1));

            var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
                    Math.Cos(ToRadians((double)lat1)) * Math.Cos(ToRadians((double)lat2)) *
                    Math.Sin(dLon / 2) * Math.Sin(dLon / 2);

            var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
            var distance = R * c;

            return Math.Round((decimal)distance, 2);
        }

        /// <summary>
        /// 将角度转换为弧度
        /// </summary>
        /// <param name="degrees">角度</param>
        /// <returns>弧度</returns>
        private static double ToRadians(double degrees)
        {
            return degrees * Math.PI / 180;
        }
    }
}